﻿
namespace Patterns4Net.Tests.PatternEnforcer.Core.Patterns.Regression
{
    using System;
    using System.Diagnostics.Contracts;
    using NUnit.Framework;
    using Patterns4Net.Core.CecilExtensions;
    using Patterns4Net.PatternEnforcer.Core.Patterns;

    /// <summary>
    /// This class contains regression tests of class <see cref="ImmutableChecker"/>.
    /// Isolated testing of ImmutableChecker would be difficult so we test it on real classes 
    /// (nested classes of ImmutableCheckerTest).
    /// </summary>
    [TestFixture]
    public class ImmutableCheckerTest : PatternCheckerTestBase
    {
        [Test]
        [TestCase(typeof(MutableThruField))]
        [TestCase(typeof(MutableThruStaticMethodWithArg))]
        [TestCase(typeof(MutableThruMethod))]
        [TestCase(typeof(Immutable))]
        [TestCase(typeof(ImmutableWithFactoryMethod))]
        [TestCase(typeof(MutableThruDeepCall))]
        [TestCase(typeof(MutableThruDeepCallWithIf))]
        [TestCase(typeof(MutableThruCallOnParameter))]
        [TestCase(typeof(ConsifusingEvilParam))]
        public void RunAllTests(Type type)
        {
            Contract.Requires(type != null);
            this.TestChecker(new ImmutableChecker(), type, new Patterns4Net.Core.Patterns.Immutable(type.GetTypeDefinition(), false));
        }

        [PatternTest(true)]
        public class ConsifusingEvilParam
        {
            private int x = 1;

            public ConsifusingEvilParam Create()
            {
                var result = new ConsifusingEvilParam();
                ChangeMyParam(result);
                return result;
            }

            private void ChangeMyParam(ConsifusingEvilParam p)
            {
                // here the p is not evil, since it didn't come from outside world 
                // we can change it's internal state...
                p.x = p.x + 3;
            }
        }

        [PatternTest(false)]
        public class MutableThruStaticMethodWithArg
        {
            private int x;

            public MutableThruStaticMethodWithArg(int x)
            {
                this.x = x;
            }

            public static void Change(MutableThruStaticMethodWithArg obj)
            {
                obj.x = 13;
            }
        }

        [PatternTest(false)]
        public class MutableThruField
        {
            public double MutableField = 3;
        }

        [PatternTest(false)]
        public class MutableThruMethod
        {
            public MutableThruMethod(int x)
            {
                this.X = x;
            }

            public int X { get; private set; }

            public void SetX(int newX)
            {
                this.X = newX;
            }
        }

        [PatternTest(true)]
        public class ImmutableWithFactoryMethod
        {
            private int x;

            public static ImmutableWithFactoryMethod Create(int newX)
            {
                var result = new ImmutableWithFactoryMethod();
                result.x = newX;
                return result;
            }
        }

        [PatternTest(true)]
        public class Immutable 
        {
            public readonly int ReadOnlyField = 3;

            public Immutable (int x)
	        {
                this.X = x;
	        }
        
            public  int X { get; private set; }

            public int FakeSetX(int x)
            {
                return this.X * x;
            }
        }

        [PatternTest(false)]
        public class MutableThruDeepCall
        {
            public int X { get; private set; }

            public void ChangeMyParam(MutableThruDeepCall p)
            {
                var x = p;
                this.ChangeMyParamImpl(x);
            }

            private void ChangeMyParamImpl(MutableThruDeepCall p)
            {
                p.X = 3;
            }
        }

        [PatternTest(false)]
        public class MutableThruDeepCallWithIf
        {
            public int X { get; private set; }

            public void ChangeMyParam(MutableThruDeepCallWithIf p)
            {
                var x = p;
                if (p.X == 10)
                {
                    x = new MutableThruDeepCallWithIf();
                }
                this.ChangeMyParamImpl(x);
            }

            private void ChangeMyParamImpl(MutableThruDeepCallWithIf p)
            {
                p.X = 3;
            }
        }

        [PatternTest(false)]
        public class MutableThruCallOnParameter
        {
            public int X { get; private set; }

            public void ChangeMyParam(MutableThruCallOnParameter p)
            {
                var x = p;
                x.ChangeMe();
            }

            private void ChangeMe()
            {
                this.X = 3;
            }
        }
    }
}
